package jEDF.JEDF;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;

import javax.swing.SwingUtilities;

import jEDF.Exceptions.EDFSignalsNotCorrespondingException;
import jEDF.EDF.*;
import jEDF.PreciseScoring.*;

/**
 *
 * <p>Titre : jEDF</p>
 * <p>Description : Java European Data Format Viewer and Analyser</p>
 * <p>Author : Nizar Kerkeni</p>
 * <p>Copyright : Copyright (c) 2003-2006</p>
 * <p>Version : 2.0</p>
 */

public class JEDFGraph implements Cloneable {
    private EDFSignal signal = null;

    // drawing of the axis
    private float dashLength = 5f;

    private float heightMultiplier = 1f;
    private Color xAxisColor = Color.LIGHT_GRAY;
    private Polysomnograph polysomnograph = null;

    private Color drawColor = Color.BLUE;
    private Color fadedColor = Color.BLACK;
    private Color highlightedColor = new Color(200, 30, 30); //Color.RED;

    private final float zoomCoef = 1.2f;

    private boolean isInverted = false;

    /**
     * @param polysomnograph the Polysomnograph who will contains this Graph
     * @param signal the signal represented by this Graph
     */
    public JEDFGraph(Polysomnograph polysomnograph, EDFSignal signal) {
        this.signal = signal;
        this.polysomnograph = polysomnograph;
    }

    /**
     * @param g the Graphics2D to draw to
     * @param yOffset the ordinate offset to start drawing from,
     * in pixels from the top of the Polysomnograph
     */
    public void draw(Graphics2D g, float yOffset) {
        double width = SwingUtilities.calculateInnerArea(polysomnograph,
                new Rectangle()).getWidth();
        double height = polysomnograph.getGraphHeight();

        drawXAxis(g, yOffset, width, height);
        drawGraph(g, yOffset, width, height);
        drawHighlightedPart(g, yOffset, width, height);
    }

    /**
     * @param g the Graphics2D to draw to
     * @param yOffset the ordinate offset to start drawing from,
     * @param width the width of the surface to use
     * @param height the height of the surface to use
     * in pixels from the top of the Polysomnograph
     */
    public void draw(Graphics2D g, float yOffset, double width, double height,
                     double startPosition, double endPosition) {
        short[] data = signal.getSelectedSignalData(polysomnograph.
                getStartSecond(), polysomnograph.getEpochDuration(),
                startPosition, endPosition);
        int[][] processedData = processData(data, 0f, yOffset, (float) width,
                                            (float) height);

        drawXAxis(g, yOffset, width, height);
        displayData(g, processedData[0], processedData[1], drawColor);
    }


    private void drawXAxis(Graphics2D g, float yOffset, double width,
                           double height) {

        float y = yOffset + (int) Math.round(height / 2.0);

        Color currentColor = g.getColor();
        g.setColor(xAxisColor);

        Stroke tempStroke = g.getStroke();

        float[] dash = {dashLength, dashLength};
        g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_SQUARE,
                                    BasicStroke.JOIN_BEVEL, 1f, dash, 0f));

        g.drawLine(0, (int) y, (int) width, (int) y);

        g.setStroke(tempStroke);
        g.setColor(currentColor);
    }

    /**
     * increase this Graph vertical size
     */
    public void increaseVerticalSize() {
        heightMultiplier *= zoomCoef;
        polysomnograph.repaint();
    }

    /**
     * decrease this Graph vertical size
     */
    public void decreaseVerticalSize() {
        heightMultiplier /= zoomCoef;
        polysomnograph.repaint();
    }

    /**
     * reset this Graph vertical size
     */
    public void resetVerticalSize() {
        heightMultiplier = 1f;
        polysomnograph.repaint();
    }


    private void drawGraph(Graphics2D g2D, float yOffset, double width,
                           double height) {
        short[] rawData = signal.readSecondsAndAPoint(polysomnograph.
                getStartSecond(), polysomnograph.getEpochDuration());
        if (rawData.length == 0) {
            return;
        }
        int[][] processedData = processData(rawData, 0f, yOffset, (float) width,
                                            (float) height); //processNormalData(rawData,yOffset);

        boolean useNormalColor = true;
        PreciseScoring preciseScoring = polysomnograph.getPreciseScoring();

        if (preciseScoring != null) {
            useNormalColor = !preciseScoring.hasFocus();
        }

        displayData(g2D, processedData[0], processedData[1],
                    (useNormalColor ? drawColor : fadedColor));
    }


    private int[][] processHighlightedData(short[] data, float yOffset,
                                           double width, double height) {

        float xOffset = (float) width;

        width /=
                ((float) polysomnograph.getEpochDuration() /
                 (float) polysomnograph.getPreciseScoring().getGranularity());

        xOffset *= (float) polysomnograph.getPreciseScoring().getCurrentPosition();
        xOffset /= (float) polysomnograph.getEpochDuration();

        return processData(data, xOffset, yOffset, (float) width,
                           (float) height);
    }

    private int[][] processData(short[] data, float xoffset, float yOffset,
                                float width, float height) {
        int[][] tempResult = {new int[0], new int[0]};

        if (data.length == 0) {
            return tempResult;
        }

        int digitalMin = signal.getDigMin();
        int digitalMax = signal.getDigMax();
        int digitalAmplitude = Math.abs(digitalMin) + Math.abs(digitalMax);

        float coefWidth = (float) (width / (double) (data.length - 1));
        float coefHeight = (float) (height / (double) digitalAmplitude);

        float abscissa = xoffset;
        int[] x = new int[data.length];
        int[] y = new int[data.length];

        //int actualPoint = 0;
        float actualPoint = 0;
        //int offset = (int)Math.round(height/2.0);
        float offset = (float) (height / 2.0);
        yOffset += offset;

        offset *= heightMultiplier;
        coefHeight *= heightMultiplier;

        if (isInverted) {
            for (int i = 0; i < data.length; i++) {
                actualPoint = (int) data[i] - digitalMin;
                actualPoint *= coefHeight;
                actualPoint -= offset;
                actualPoint += yOffset;

                y[i] = (int) actualPoint;
                x[i] = (int) abscissa;
                abscissa += coefWidth;
            }
        }
        else {
            for (int i = 0; i < data.length; i++) {
                actualPoint = (int) data[i] - digitalMin;
                actualPoint *= coefHeight;
                actualPoint -= offset;
                actualPoint = -actualPoint + (int) yOffset;

                y[i] = (int) actualPoint;
                x[i] = (int) abscissa;
                abscissa += coefWidth;
            }
        }
        int[][] result = {x, y};

        return result;
    }

    private void displayData(Graphics2D g, int[] x, int[] y, Color color) {
        Color originalColor = g.getColor();
        g.setColor(color);

        g.drawPolyline(x, y, x.length);
        g.setColor(originalColor);
    }

    /**
     * @return true if the Graph is drawn inverted along the abscissa axis
     */
    public boolean isInverted() {
        return isInverted;
    }

    /**
     * @param bInverted set whether the graph is to be draw inverted along the abscissa axis
     */
    public void setInverted(boolean bInverted) {
        isInverted = bInverted;
        polysomnograph.repaint();
    }

    /* (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    public Object clone() {
        JEDFGraph result = new JEDFGraph(polysomnograph, signal);

        result.drawColor = drawColor;
        result.heightMultiplier = heightMultiplier;
        result.isInverted = isInverted;
        result.xAxisColor = xAxisColor;
        result.polysomnograph = polysomnograph;

        return result;
    }

    /**
     * @param graph the graph
     * @param bipolarSignal the signal to display
     */
    public JEDFGraph(JEDFGraph graph, EDFBipolarSignal bipolarSignal) {
        this.signal = bipolarSignal;
        this.heightMultiplier = graph.heightMultiplier;
        this.xAxisColor = graph.xAxisColor;
        this.drawColor = graph.drawColor;
        this.isInverted = graph.isInverted;
        this.polysomnograph = graph.polysomnograph;
    }

    /**
     * @return Returns the corresponding signal.
     */
    public EDFSignal getSignal() {
        return signal;
    }

    /**
     * Create a Graph representing a bipolar signal given two Graph
     * @param graph1 the first Graph to get a signal from
     * @param graph2 the second Graph to get a signal from
     * @throws EDFSignalsNotCorrespondingException if the two Graph aren't compatible
     */
    public JEDFGraph(JEDFGraph graph1, JEDFGraph graph2)
            throws
            EDFSignalsNotCorrespondingException {
        this(graph1, new EDFBipolarSignal(graph1, graph2));
    }

    private void drawHighlightedPart(Graphics2D g2D, float yOffset,
                                     double width, double height) {
        PreciseScoring preciseScoring = polysomnograph.getPreciseScoring();

        if (preciseScoring == null) {
            return;
        }
        if (!preciseScoring.hasFocus()) {
            return;
        }

        int startSecond = preciseScoring.getCurrentPosition();
        int duration = preciseScoring.getGranularity();

        startSecond += polysomnograph.getStartSecond();

        short[] rawData = signal.readSecondsAndAPoint(startSecond, duration);
        //short[] rawData = signal.readSeconds(startSecond,duration);
        if (rawData.length == 0) {
            return;
        }
        int[][] processedData = processHighlightedData(rawData, yOffset, width,
                height);
        displayData(g2D, processedData[0], processedData[1], highlightedColor);
    }

    /**
     * @return the zoom factor
     */
    public float getZoomFactor() {
        return heightMultiplier;
    }
}
